﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace Ewk.Runtime.Serialization
{
    public class DatarecordSerializer<T>
    {
        static readonly Dictionary<Type, object> _datarecordInfoDictionary = new Dictionary<Type, object>();
        static readonly object _locker = new object();

        static DatarecordInfo<T> GetDatarecordInfo()
        {
            if (!_datarecordInfoDictionary.ContainsKey(typeof(T)))
            {
                lock (_locker)
                {
                    if (!_datarecordInfoDictionary.ContainsKey(typeof(T)))
                    {
                        _datarecordInfoDictionary[typeof(T)] = new DatarecordInfo<T>();
                    }
                }
            }

            return _datarecordInfoDictionary[typeof(T)] as DatarecordInfo<T>;
        }

        /// <summary>
        /// Initializes a new instance.
        /// </summary>
        public DatarecordSerializer()
        {
            DatarecordInfo = GetDatarecordInfo();
        }

        /// <summary>
        /// Creates a <see cref="string"/> representation of the specified <paramref name="datarecord"/>.
        /// </summary>
        /// <param name="datarecord">The datarecord to be serialized.</param>
        /// <returns>A <see cref="string"/> representation of the specified <paramref name="datarecord"/>.</returns>
        public string Serialize(T datarecord)
        {
            Subscribe();
            var result = DatarecordInfo.DatarecordAttribute.Serialize<T>(datarecord, DatarecordInfo.DatafieldAttributes);
            Unsubscribe();

            return result;
        }

        /// <summary>
        /// Creates the <typeparamref name="T"/> representation of the specified <see cref="string"/>.
        /// </summary>
        /// <param name="datarecord">The datarecord to be deserialized.</param>
        /// <returns>The <typeparamref name="T"/> representation of the specified <see cref="string"/>.</returns>
        public T Deserialize(string datarecord)
        {
            Subscribe();
            var result = DatarecordInfo.DatarecordAttribute.Deserialize<T>(datarecord, DatarecordInfo.DatafieldAttributes);
            Unsubscribe();

            return result;
        }

        /// <summary>
        /// Occurs when the content of a datarecord subseeds the defined length of the datarecord.
        /// </summary>
        public event EventHandler<DatarecordIncompleteEventArgs> DatarecordIncomplete;

        void OnDatarecordIncomplete(object sender, DatarecordIncompleteEventArgs e)
        {
            if (DatarecordIncomplete == null)
            {
                throw new DatarecordIncompleteException(e.Message);
            }

            DatarecordIncomplete(sender, e);
        }

        /// <summary>
        /// Occurs when the content of a datarecord succeeds the defined length of the datarecord.
        /// </summary>
        public event EventHandler<DatarecordOverflowEventArgs> DatarecordOverflow;

        void OnDatarecordOverflow(object sender, DatarecordOverflowEventArgs e)
        {
            if (DatarecordOverflow == null)
            {
                throw new DatarecordOverflowException(e.Message);
            }

            DatarecordOverflow(sender, e);
        }

        /// <summary>
        /// Occurs when the content of a field succeeds the defined length of the field.
        /// </summary>
        public event EventHandler<DatafieldOverflowEventArgs> DatafieldOverflow;

        void OnDatafieldOverflow(object sender, DatafieldOverflowEventArgs e)
        {
            if (DatafieldOverflow == null)
            {
                throw new DatafieldOverflowException(e.Message);
            }

            DatafieldOverflow(sender, e);
        }

        /// <summary>
        /// Occurs when the content of a mandatory field is empty.
        /// </summary>
        public event EventHandler<DatafieldEmptyEventArgs> DatafieldEmpty;

        void OnDatafieldEmpty(object sender, DatafieldEmptyEventArgs e)
        {
            if (DatafieldEmpty == null)
            {
                throw new DatafieldEmptyException(e.Message);
            }

            DatafieldEmpty(sender, e);
        }

        DatarecordInfo<T> DatarecordInfo { get; set; }

        void Subscribe()
        {
            DatarecordInfo.DatarecordAttribute.DatarecordOverflow += OnDatarecordOverflow;
            DatarecordInfo.DatarecordAttribute.DatarecordIncomplete += OnDatarecordIncomplete;

            foreach (var datafieldAttribute in DatarecordInfo.DatafieldAttributes)
            {
                datafieldAttribute.DatafieldOverflow += OnDatafieldOverflow;
                datafieldAttribute.DatafieldEmpty += OnDatafieldEmpty;
            }
        }

        void Unsubscribe()
        {
            DatarecordInfo.DatarecordAttribute.DatarecordOverflow -= OnDatarecordOverflow;
            DatarecordInfo.DatarecordAttribute.DatarecordIncomplete -= OnDatarecordIncomplete;

            foreach (var datafieldAttribute in DatarecordInfo.DatafieldAttributes)
            {
                datafieldAttribute.DatafieldOverflow -= OnDatafieldOverflow;
                datafieldAttribute.DatafieldEmpty -= OnDatafieldEmpty;
            }
        }
    }
}
